Programming With QuickTime Sprites
This appendix includes sample code from QTWiredSprite.c . For information on how to use this sample code in your application, see Chapter 2, "Sprite Media Handler," and Chapter 3, "Wired Sprites."
//////////
//
// File: QTWiredSprites.c
//
// Contains: QuickTime wired sprites support for QuickTime movies.
//
// Written by: Sean Allen
// Revised by: Chris Flick and Tim Monroe
// Based (heavily!) on the existing MakeActionSpriteMovie.c
// code written by Sean Allen.
//
// Copyright: © 1997-1998 by Apple Computer, Inc., all rights reserved.
//
// Change History (most recent first):
//
// <2> 03/26/98 rtm made fixes for Windows compiles
// <1> 03/25/98 rtm first file; integrated existing code
// with shell framework
//
//
// This sample code creates a wired sprite movie containing one sprite
// track. The sprite track contains six sprites: two penguins and four
// buttons.
//
// The four buttons are initially invisible. When the mouse enters (or
// "rolls over") a button, it appears.
// When the mouse is clicked inside a button, its image changes to its
// "pressed" image. When the mouse
// is released, its image changes back to its "unpressed" image. If the
// mouse is released inside the button,
// an action is triggered. The buttons perform the actions of go to
// beginning of movie, step backward,
// step forward, and go to end of movie.
//
// The first penguin shows all of the buttons when the mouse enters it,
// and hides them when the mouse exits.
// The first penguin is the only sprite that has properties that are
// overriden by the override sprite samples.
// These samples override its matrix (in order to move it) and its image
// index (in order to make it "waddle").
//
// When the mouse is clicked on the second penguin, it changes its image
// index to its "eyes closed" image.
// When the mouse is released, it changes back to its normal image. This
// makes it appear to blink when clicked on.
// When the mouse is released over the penguin, several actions are
// triggered. Both penguins' graphics states are
// toggled between copyMode and blendMode, and the movie's rate is
// toggled between zero and one.
//
// The second penguin moves once per second. This occurs whether the
// movie's rate is currently zero or one,
// because it is being triggered by a gated idle event. When the penguin
// receives the idle event, it changes
// its matrix using an action which uses min, max, delta, and wraparound
// options.
//
// The movie's looping mode is set to palindrome by a frame-loaded
// action.
//
// So, our general strategy is as follows (though perhaps not in the
// order listed):
//
// (1) Create a new movie file with a single sprite track.
// (2) Assign the "no controller" movie controller to the movie.
// (3) Set the sprite track's background color, idle event
// frequency, and hasActions properties.
// (4) Convert our PICT resources to animation codec images with
// transparency.
// (5) Create a key frame sample containing six sprites and all of
// their shared images.
// (6) Assign the sprites their initial property values.
// (7) Create a frameLoaded event for the key frame.
// (8) Create some override samples that override the matrix and
// image index properties of the first penguin sprite.
//
// NOTES:
//
// *** (1) ***
// There are event types other that mouse related events (for instance,
// Idle and FrameLoaded).
// Idle events are independent of the movie's rate, and they can be
// gated so they are send at most
// every n ticks. In our sample movie, the second penguin moves when the
// movie's rate is zero,
// and moves only once per second because of the value of the sprite
// track's idleEventFrequencey property.
//
// *** (2) ***
// Multiple actions may be executed in response to a single event. In
// our sample movie, rolling over
// the first penguin shows and hides four different buttons.
//
// *** (3) ***
// Actions may target any sprite or track in the movie. In our sample
// movie, clicking on one penguin
// changes the graphics mode of the other.
//
// *** (4) ***
// Conditional and looping control structures are supported. In our
// sample movie, the second penguin
// uses the "case statement" action.
//
// *** (5) ***
// Sprite track variables that have not been set have a default value of
// zero. (The second penguin's
// conditional code relies on this.)
//
// *** (6) ***
// Wired sprites were previously known as "action sprites". Don't let
// the names of some of the utility
// functions confuse you. We'll try to update the source code as time
// permits.
//
// *** (7) ***
// Penguins don't fly, but I hear they totally shred halfpipes on
// snowboards.
//
//////////
// header files
#include "QTWiredSprites.h"
//////////
//
// QTWired_CreateWiredSpritesMovie
// Create a QuickTime movie containing a wired sprites track.
//
//////////
OSErr QTWired_CreateWiredSpritesMovie (void)
{
short myResRefNum = 0;
Movie myMovie = NULL;
Track myTrack;
Media myMedia;
StandardFileReply myReply;
QTAtomContainer mySample = NULL;
QTAtomContainer myActions = NULL;
QTAtomContainer myBeginButton, myPrevButton, myNextButton,
myEndButton;
QTAtomContainer myPenguinOne, myPenguinTwo,
myPenguinOneOverride;
QTAtomContainer myBeginActionButton, myPrevActionButton,
myNextActionButton, myEndActionButton;
QTAtomContainer myPenguinOneAction, myPenguinTwoAction;
RGBColor myKeyColor;
Point myLocation;
short isVisible, myLayer, myIndex, myResID, i,
myDelta;
Boolean hasActions;
long myFlags = createMovieFileDeleteCurFile |
createMovieFileDontCreateResFile;
OSType myType = FOUR_CHAR_CODE('none');
UInt32 myFrequency;
QTAtom myEventAtom;
long myLoopingFlags;
ModifierTrackGraphicsModeRecord myGraphicsMode;
OSErr myErr = noErr;
//////////
//
// create a new movie file and set its controller type
//
//////////
// ask the user for the name of the new movie file
StandardPutFile("\pSprite movie file name:", "\pSprite.mov",
&myReply);
if (!myReply.sfGood)
goto bail;
// create a movie file for the destination movie
myErr = CreateMovieFile(&myReply.sfFile, FOUR_CHAR_CODE('TVOD'), 0,
myFlags, &myResRefNum, &myMovie);
if (myErr != noErr)
goto bail;
// select the "no controller" movie controller
myType = EndianU32_NtoB(myType);
SetUserDataItem(GetMovieUserData(myMovie), &myType, sizeof(myType),
kUserDataMovieControllerType, 1);
//////////
//
// create the sprite track and media
//
//////////
myTrack = NewMovieTrack(myMovie, ((long)kSpriteTrackWidth << 16),
((long)kSpriteTrackHeight << 16), kNoVolume);
myMedia = NewTrackMedia(myTrack, SpriteMediaType, kSpriteMediaTimeScale, NULL, 0);
//////////
//
// create a key frame sample containing six sprites and all of their
// shared images
//
//////////
// create a new, empty key frame sample
myErr = QTNewAtomContainer(&mySample);
if (myErr != noErr)
goto bail;
myKeyColor.red = 0xffff; // white
myKeyColor.green = 0xffff;
myKeyColor.blue = 0xffff;
// add images to the key frame sample
AddPICTImageToKeyFrameSample(mySample, kGoToBeginningButtonUp,
&myKeyColor, kGoToBeginningButtonUpIndex, NULL, NULL);
AddPICTImageToKeyFrameSample(mySample, kGoToBeginningButtonDown,
&myKeyColor, kGoToBeginningButtonDownIndex, NULL, NULL);
AddPICTImageToKeyFrameSample(mySample, kGoToEndButtonUp, &myKeyColor,
kGoToEndButtonUpIndex, NULL, NULL);
AddPICTImageToKeyFrameSample(mySample, kGoToEndButtonDown,
&myKeyColor, kGoToEndButtonDownIndex, NULL, NULL);
AddPICTImageToKeyFrameSample(mySample, kGoToPrevButtonUp,
&myKeyColor, kGoToPrevButtonUpIndex, NULL, NULL);
AddPICTImageToKeyFrameSample(mySample, kGoToPrevButtonDown,
&myKeyColor, kGoToPrevButtonDownIndex, NULL, NULL);
AddPICTImageToKeyFrameSample(mySample, kGoToNextButtonUp,
&myKeyColor, kGoToNextButtonUpIndex, NULL, NULL);
AddPICTImageToKeyFrameSample(mySample, kGoToNextButtonDo
bail:
if (mySample != NULL)
QTDisposeAtomContainer(mySample);
if (myBeginButton != NULL)
QTDisposeAtomContainer(myBeginButton);
if (myPrevButton != NULL)
QTDisposeAtomContainer(myPrevButton);
if (myNextButton != NULL)
QTDisposeAtomContainer(myNextButton);
if (myEndButton != NULL)
QTDisposeAtomContainer(myEndButton);
if (myResRefNum != 0)
CloseMovieFile(myResRefNum);
if (myMovie != NULL)
DisposeMovie(myMovie);
return(myErr);
}
//////////
//
// QTWired_AddPenguinTwoConditionalActions
// Add actions to the second penguin that transform him (her?) into a two
// state button
// that plays or pauses the movie.
//
// We are relying on the fact that a "GetVariable" for a variable ID
// which has never been set
// will return zero. If we needed a different default value, we could
// initialize it using the
// frameLoaded event.
//
// A higher-level description of the logic is:
//
// On MouseUpInside
// If (GetVariable(DefaultTrack, 1) = 0)
// SetMovieRate(1)
// SetSpriteGraphicsMode(DefaultSprite, { blend, grey } )
// SetSpriteGraphicsMode(GetSpriteByID(DefaultTrack, 5),
// { ditherCopy, white } )
// SetVariable(DefaultTrack, 1, 1)
// ElseIf (GetVariable(DefaultTrack, 1) = 1)
// SetMovieRate(0)
// SetSpriteGraphicsMode(DefaultSprite, { ditherCopy, white })
// SetSpriteGraphicsMode(GetSpriteByID(DefaultTrack, 5),
// { blend, grey })
// SetVariable(DefaultTrack, 1, 0)
// Endif
// End
//
//////////
OSErr QTWired_AddPenguinTwoConditionalActions (QTAtomContainer
theContainer, QTAtom theEventAtom)
{
QTAtom myNewActionAtom, myNewParamAtom, myConditionalAtom;
QTAtom myExpressionAtom, myOperatorAtom, myActionListAtom;
short myParamIndex, myConditionIndex, myOperandIndex;
float myConstantValue;
QTAtomID myVariableID;
ModifierTrackGraphicsModeRecord myBlendMode, myCopyMode;
OSErr myErr = noErr;
myBlendMode.graphicsMode = blend;
myBlendMode.opColor.red = myBlendMode.opColor.green =
myBlendMode.opColor.blue = 0x8fff; // grey
myCopyMode.graphicsMode = ditherCopy;
myCopyMode.opColor.red = myCopyMode.opColor.green =
myCopyMode.opColor.blue = 0xffff; // white
AddActionAtom(theContainer, theEventAtom, kActionCase,
&myNewActionAtom);
myParamIndex = 1;
AddActionParameterAtom(theContainer, myNewActionAtom, myParamIndex,
0, NULL, &myNewParamAtom);
// first condition
myConditionIndex = 1;
AddConditionalAtom(theContainer, myNewParamAtom, myConditionIndex,
&myConditionalAtom);
AddExpressionContainerAtomType(theContainer, myConditionalAtom,
&myExpressionAtom);
AddOperatorAtom(theContainer, myExpressionAtom, kOperatorEqualTo,
&myOperatorAtom);
myOperandIndex = 1;
myConstantValue = kButtonStateOne;
AddOperandAtom(theContainer, myOperatorAtom, kOperandConstant,
myOperandIndex, NULL, myConstantValue);
myOperandIndex = 2;
myVariableID = kPenguinStateVariableID;
AddVariableOperandAtom(theContainer, myOperatorAtom, myOperandIndex,
0, NULL, 0, myVariableID);
AddActionListAtom(theContainer, myConditionalAtom,
&myActionListAtom);
AddMovieSetRateAction(theContainer, myActionListAtom, 0,
Long2Fix(1));
AddSpriteSetGraphicsModeAction(theContainer, myActionListAtom, 0, 0,
NULL, 0, 0, NULL, &myBlendMode, NULL);
AddSpriteSetGraphicsModeAction(theContainer, myActionListAtom, 0, 0,
NULL, 0, kTargetSpriteID, (void *)kPenguinOneSpriteID,
&myCopyMode, NULL);
AddSpriteTrackSetVariableAction(theContainer, myActionListAtom, 0,
kPenguinStateVariableID, kButtonStateTwo, 0, NULL, 0);
// second condition
myConditionIndex = 2;
AddConditionalAtom(theContainer, myNewParamAtom, myConditionIndex,
&myConditionalAtom);
AddExpressionContainerAtomType(theContainer, myConditionalAtom,
&myExpressionAtom);
AddOperatorAtom(theContainer, myExpressionAtom, kOperatorEqualTo,
&myOperatorAtom);
myOperandIndex = 1;
myConstantValue = kButtonStateTwo;
AddOperandAtom(theContainer, myOperatorAtom, kOperandConstant,
myOperandIndex, NULL, myConstantValue);
myOperandIndex = 2;
myVariableID = kPenguinStateVariableID;
AddVariableOperandAtom(theContainer, myOperatorAtom, myOperandIndex,
0, NULL, 0, myVariableID);
AddActionListAtom(theContainer, myConditionalAtom,
&myActionListAtom);
AddMovieSetRateAction(theContainer, myActionListAtom, 0,
Long2Fix(0));
AddSpriteSetGraphicsModeAction(theContainer, myActionListAtom, 0, 0,
NULL, 0, 0, NULL, &myCopyMode, NULL);
AddSpriteSetGraphicsModeAction(theContainer, myActionListAtom, 0, 0,
NULL, 0, kTargetSpriteID, (void *)kPenguinOneSpriteID,
&myBlendMode, NULL);
AddSpriteTrackSetVariableAction(theContainer, myActionListAtom, 0,
kPenguinStateVariableID, kButtonStateOne, 0, NULL, 0);
bail:
return(myErr);
}
//////////
//
// QTWired_AddWraparoundMatrixOnIdle
// Add beginning, end, and change matrices to the specified atom
// container.
//
//////////
OSErr QTWired_AddWraparoundMatrixOnIdle (QTAtomContainer theContainer)
{
MatrixRecord myMinMatrix, myMaxMatrix, myDeltaMatrix;
long myFlags = kActionFlagActionIsDelta |
kActionFlagParameterWrapsAround;
QTAtom myActionAtom;
OSErr myErr = noErr;
myMinMatrix.matrix[0][0] = myMinMatrix.matrix[0][1] =
myMinMatrix.matrix[0][2] = EndianS32_NtoB(0xffffffff);
myMinMatrix.matrix[1][0] = myMinMatrix.matrix[1][1] =
myMinMatrix.matrix[1][2] = EndianS32_NtoB(0xffffffff);
myMinMatrix.matrix[2][0] = myMinMatrix.matrix[2][1] =
myMinMatrix.matrix[2][2] = EndianS32_NtoB(0xffffffff);
myMaxMatrix.matrix[0][0] = myMaxMatrix.matrix[0][1] =
myMaxMatrix.matrix[0][2] = EndianS32_NtoB(0x7fffffff);
myMaxMatrix.matrix[1][0] = myMaxMatrix.matrix[1][1] =
myMaxMatrix.matrix[1][2] = EndianS32_NtoB(0x7fffffff);
myMaxMatrix.matrix[2][0] = myMaxMatrix.matrix[2][1] =
myMaxMatrix.matrix[2][2] = EndianS32_NtoB(0x7fffffff);
myMinMatrix.matrix[2][1] = EndianS32_NtoB(Long2Fix((1 *
kSpriteTrackHeight / 4) - (kPenguinHeight / 2)));
myMaxMatrix.matrix[2][1] = EndianS32_NtoB(Long2Fix((3 *
kSpriteTrackHeight / 4) - (kPenguinHeight / 2)));
SetIdentityMatrix(&myDeltaMatrix);
myDeltaMatrix.matrix[2][1] = Long2Fix(1);
// change location
myErr = AddSpriteSetMatrixAction(theContainer,
kParentAtomIsContainer, kQTEventIdle, 0, NULL, 0, 0, NULL,
&myDeltaMatrix, &myActionAtom);
if (myErr != noErr)
goto bail;
myErr = AddActionParameterOptions(theContainer, myActionAtom, 1,
myFlags, sizeof(myMinMatrix), &myMinMatrix,
sizeof(myMaxMatrix), &myMaxMatrix);
bail:
return(myErr);
}